昨天把 LLM serving 的速度拆成 TTFT 與 ITL。今天會發現,這兩個指標背後剛好是兩種不同的工作。
如果兩者共用同一批 GPUs,長 Prefill 可能插進正在 Decode 的 batch,讓其他 requests 的下一個 token 等更久。Chunked Prefill 可以減輕干擾;另一條路則是直接把兩個階段放到不同 workers。
一般 aggregated serving 在同一個 instance 裡完成兩個階段:
Request
│
▼
同一組 GPU Workers
├─ Prefill A
├─ Decode B、C、D
├─ Prefill E
└─ Decode A、B、C、D、E
好處是簡單,而且 prompt 產生的 KV Cache 已經留在原本的 GPU workers 上,不需要跨 instance 搬家。
Prefill–Decode Disaggregation 則多了一次 handoff。這篇用 Ray Serve + vLLM 當成具體架構:
Client → Ray Serve OpenAI Ingress → PDDecodeServer
│ remote prefill
▼
PDPrefillServer / vLLM P
│
KV Cache Connector
NIXL/LMCache
│
▼
vLLM D
│
streamed tokens
Ray Serve 建立 Prefill、Decode deployments,處理 placement、replicas、autoscaling 與整個 P → D request flow。Prefill worker 讀取 prompt 並建立每一層的 K、V;connector 再把 KV Cache 與協調資訊交給 Decode worker。Decode 不必重算 prompt,載入 KV 後便能繼續 autoregressive generation。
這裡要分清楚 control plane 和 data plane:
| 層 | 責任 |
|---|---|
| Ray Serve | 部署、路由、獨立擴縮,協調 request 先 P 後 D |
| vLLM | 在兩組 engines 上執行 Prefill 與 Decode |
| NIXL/LMCache | 真正傳送或共享 KV Cache |
所以 Ray 把兩個服務「接起來」,但 KV tensors 並不是透過 Ray object store 搬運。
這樣做有兩個主要目的:
對一般 Transformer,單條 prompt 的 KV Cache 大小可以手算:
KV bytes
= 2 × layers × KV heads × head_dim × prompt tokens × bytes_per_element
假設模型有 32 層、8 個 KV heads、head_dim = 128,使用 BF16,prompt 長度為 4096:
2 × 32 × 8 × 128 × 4096 × 2 bytes
= 536,870,912 bytes
= 512 MiB
這 512 MiB 就是 Prefill 和 Decode 之間必須交接的主要狀態。忽略協定與同步成本,傳輸時間的理想下界是:
T_transfer ≥ KV bytes / effective bandwidth
| 假設有效頻寬 | 512 MiB 的理想傳輸時間 |
|---|---|
| 25 GB/s | 約 21.5 ms |
| 100 GB/s | 約 5.4 ms |
這不是實測值。真實時間還包含排隊、metadata、同步與小傳輸效率;某些實作也會讓 KV layer-by-layer 傳送,嘗試把傳輸和計算重疊。
Disaggregation 是否值得,可以先看這個不等式:
避免的 P/D interference + 獨立擴縮帶來的 SLO 收益
>
KV transfer + 額外 queueing + model weights 複製 + pool imbalance
其中最容易漏掉的是 model weights 複製:Prefill 與 Decode instances 都要能執行同一個模型,兩邊都需要權重和自己的 GPU memory。若流量太小,其中一個 pool 還可能長時間閒置。
| 比較適合拆開 | 可能不划算 |
|---|---|
| 長 prompts、同時有大量 Decode | prompts 很短、流量低 |
| tail ITL SLO 很嚴格 | 單機或低速網路 |
| P、D 負載比例可預測 | P/D 流量容易嚴重失衡 |
| 有高頻寬互連與穩定 routing | KV 很大但 transfer 無法重疊 |
所以要比較的不是單一 request 是否少了幾毫秒,而是 Day 19 的問題:在 TTFT 與 TPOT SLO 內,每張 GPU 能提供多少 goodput。
Ray Serve LLM 現在提供 build_pd_openai_app。它接收兩份設定:
prefill_config → Prefill deployment → vLLM KV producer
decode_config → Decode deployment → vLLM KV consumer
兩份設定可以使用不同 GPU 類型、engine parameters 與 autoscaling replica 範圍。Ray 的 PDDecodeServer 負責呼叫遠端 Prefill,取得 KV metadata,再由本地 Decode engine 載入 KV 並把 tokens stream 回 client。
vLLM 端仍要設定 KV connector。NIXL 可以透過 UCX、libfabric 或 EFA 傳輸;LMCache 則能再接不同的 cache/storage backend。Ray 解決服務編排,connector 解決 tensor transport。
目前 vLLM upstream 的 Disaggregated Prefilling 仍標為 experimental。官方文件把目標寫得很保守:分開調整 TTFT/ITL、降低 tail ITL,而不是保證提高 raw throughput。Ray Serve 讓部署和擴縮更完整,但不會消除 KV transfer、版本相容性或 P/D imbalance。
這也說明 Disaggregation 不是單純增加一個網路傳輸 API;router、KV lifetime、失敗處理、cache locality 與 P/D 負載平衡都變成 serving system 的一部分。
Aggregated:少一次 KV transfer,架構簡單,但 P/D 會互相干擾
Disaggregated:分開調 TTFT 與 ITL,但要支付 KV 搬運與資源複製成本
Ray Serve:control plane;vLLM:compute engine;NIXL/LMCache:KV data plane
拆開不代表一定更快。它真正購買的是 performance isolation 與獨立擴縮能力;只有省下的 interference 與 SLO violations 大於 handoff 成本時,才值得。
下一篇會進入 Speculative Decoding:多花 draft model 的計算,真的能更快產生 token 嗎?